/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.tools.idea.databinding.finders

import com.android.tools.idea.databinding.module.LayoutBindingModuleCache
import com.android.tools.idea.databinding.util.DataBindingUtil
import com.android.tools.idea.databinding.util.isViewBindingEnabled
import com.android.tools.idea.projectsystem.getModuleSystem
import com.android.tools.idea.util.androidFacet
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleUtil
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.ResolveScopeEnlarger
import com.intellij.psi.search.SearchScope
import com.intellij.psi.util.CachedValueProvider
import com.intellij.psi.util.CachedValuesManager
import com.intellij.psi.util.PsiModificationTracker
import org.jetbrains.android.facet.AndroidFacet
import org.jetbrains.kotlin.idea.base.projectStructure.KotlinResolveScopeEnlarger

private fun AndroidFacet.isRelevantForScopeEnlarging(): Boolean {
  return DataBindingUtil.isDataBindingEnabled(this) || isViewBindingEnabled()
}

/**
 * Scope enlarger for data binding and view binding modules, providing additional in-memory light
 * classes that represent files that will eventually be generated by the data binding compiler.
 */
class BindingScopeEnlarger : ResolveScopeEnlarger() {
  override fun getAdditionalResolveScope(file: VirtualFile, project: Project): SearchScope? {
    val module = ModuleUtil.findModuleForFile(file, project) ?: return null
    val facet = module.androidFacet?.takeIf { it.isRelevantForScopeEnlarging() } ?: return null

    return getAdditionalResolveScope(facet)
  }
}

/** Additional scope enlarger for Kotlin. */
class BindingKotlinScopeEnlarger : KotlinResolveScopeEnlarger {
  override fun getAdditionalResolveScope(module: Module, isTestScope: Boolean): SearchScope? {
    val facet = module.androidFacet?.takeIf { it.isRelevantForScopeEnlarging() } ?: return null
    return getAdditionalResolveScope(facet)
  }
}

private fun getAdditionalResolveScope(facet: AndroidFacet): SearchScope? {
  val module = facet.module
  val project = module.project
  return CachedValuesManager.getManager(project).getCachedValue(module) {
    val localScope = facet.toLightBindingClassSearchScope()
    val scopeIncludingDeps =
      module
        .getModuleSystem()
        .getResourceModuleDependencies()
        .mapNotNull { module -> module.androidFacet?.toLightBindingClassSearchScope() }
        .fold(localScope) { scopeAccum, depScope -> scopeAccum.union(depScope) }

    CachedValueProvider.Result.create(scopeIncludingDeps, PsiModificationTracker.MODIFICATION_COUNT)
  }
}

private fun AndroidFacet.toLightBindingClassSearchScope() =
  LayoutBindingModuleCache.getInstance(this).lightBindingClassSearchScope
